import logging
import os
import sys

import configobj
import validate

SERVER_SECTION = 'Server'
CHANNEL_SECTION = 'Channel'
EXT_SECTION = 'Ext'

class ConfigError(Exception):
    pass

_config = None

def save():
    logging.critical('Save config not implemented yet!')

def set_config(section, key, value):
    if not _config:
        raise ConfigError('Config Not Loaded')

    if not _config.has_key(section):
        raise ConfigError('Section Not Exist: %s' % section)

    config_section = _config[section]

    config_section[key] = value

def get_config(section, key, default=None):
    if not _config:
        raise ConfigError('Config Not Loaded')

    if not _config.has_key(section):
        if default is not None:
            return default
        else:
            raise ConfigError('Section Not Exist: %s' % section)

    config_section = _config[section]
    if not config_section.has_key(key):
        if default is not None:
            return default
        else:
            raise ConfigError('Config Not Exist: %s.%s' % (section, key))

    return config_section[key]

def get_data_path(relative_path):
    path = None
    if sys.platform == 'darwin':
        path = '~/Library/Application Support/PettyFun/Shortcut/%s' % relative_path
    else:
        path = '~/.PettyFun/Shortcut/%s' % relative_path
    path = os.path.expanduser(path)
    return path

def init():
    path = get_data_path('shortcut.conf')
    _load_config(path)

def _load_config(filename, config_spec_sections=None):
    global _config
    if _config is not None:
        logging.critical('Config Error: Try to load config again')
        raise ConfigError('Config Already Loaded')
    if config_spec_sections is not None:
        config_spec_lines = reduce(lambda a, b: a + b, config_spec_sections)
    logging.info('Loading Config: %s' % filename)
    try:
        config_spec = None
        if config_spec_sections is not None:
            config_spec = configobj.ConfigObj(config_spec_lines,
                          interpolation=False, list_values=False)
                          # Can not use _inspec=True in configobj 4.5.2 (lenny)

        config_obj = configobj.ConfigObj(filename,
                     configspec=config_spec, unrepr=True)
        if config_spec_sections is not None:
            _validate_config(config_obj)
        _config = config_obj
        logging.info('Config = %s' % _config)
    except Exception, e:
        logging.critical('Can not load config: %s' % filename)
        try:
            f = open(filename)
            lines = f.readlines()
            logging.critical('--------------------------------------\n%s'
                             % '\n'.join(lines))
            logging.error('--------------------------------------')
        except IOError, ioe:
            logging.critical('File IO Error: %s' % ioe, exc_info=True)
        raise

def _get_extra_values(config_obj):
    if configobj.__version__ < '4.7.0':
        logging.error('Can not catch extra config values in configobj %s' %
                      configobj.__version__)
        return None

    extra_values = configobj.get_extra_values(config_obj)

    for sections, name in extra_values:
        # this code gets the extra values themselves
        the_section = config_obj
        for section in sections:
            the_section = the_section[section]

        # the_value may be a section or a value
        the_value = the_section[name]

        section_or_value = 'value'
        if isinstance(the_value, dict):
            # Sections are subclasses of dict
            section_or_value = 'section'

        section_string = ', '.join(sections) or 'top level'
        logging.critical('Config Error: Extra entry in section: %s. Entry %r is a %s' %
                         (section_string, name, section_or_value))
    return extra_values

def _validate_config(config_obj):
    validate_result = config_obj.validate(validate.Validator(),
                                          preserve_errors=True)
    validate_errors = configobj.flatten_errors(config_obj, validate_result)

    for section_list, key, error in validate_errors:
        if key is not None:
            section_list.append(key)
            if not error:
                error = 'Missing Value'
        else:
            if not error:
                error = 'Missing Section'
        section_string = '.'.join(section_list)
        logging.critical('Config Error: [%s] (%s)' % (section_string, error))

    extra_values = _get_extra_values(config_obj)

    if validate_errors or extra_values:
        raise ConfigError('Bad Configuration: see log for more details.')

